<?php
namespace july\julyadmin\uploaddrives\drives;

use think\Validate;
use \july\julyadmin\common\model\Config;
use \july\julyadmin\common\model\Files;

/**
 * 本地文件驱动
 */
class fileUpload
{
    //驱动id
    public $drive_id = 'location';
    //驱动id
    public $drive_name = '本地';
    //配置表单
    public $form;
    //初始化
    public function __construct()
    {
        $this->form = function (\july\julyadmin\builder\Form &$form, $data) {

        };
    }

    /**
     * 图片上传
     * @return json
     */
    public function imageUpload()
    {
        sleep(1);
        // 获取表单上传文件
        $files = request()->file();
        $data  = input('post.');
        if (!isset($data['chunks'])) {
            $data['chunks'] = 1;
        }
        if (!isset($data['chunk'])) {
            $data['chunk'] = 0;
        }
        try {
            if (!$files) {
                return ['code' => 0, 'msg' => '上传文件为空'];
            }
            $config   = Config::config('ImageUpload');
            $validate = ['image' => []];
            //拼接图片验证规则
            $validate_rule = array();
            if (isset($config["imageMaxSize"]) && $config["imageMaxSize"]) {
                $validate_rule[] = "fileSize:" . $config["imageMaxSize"];
            }
            if (isset($config["imageAllowFiles"]) && $config["imageAllowFiles"]) {
                $validate_rule[] = "fileExt:" . $config["imageAllowFiles"];
            }

            if ($validate_rule) {
                $validate['image'] = implode("|", $validate_rule);
            }

            try {
                validate($validate)->check($files);
            } catch (\think\exception\ValidateException $e) {
                return ['code' => 0, 'msg' => $e->getError()];
            }

            $fileinfo;
            //上传文件
            foreach ($files as $file) {
                //如果分片还在上传中
                $path = '../runtime' . DIRECTORY_SEPARATOR . $data['md5'];
                $path = str_ireplace("\\", "/", $path);
                //创建目录
                if (!is_readable($path)) {
                    is_file($path) or mkdir($path, 0777, true);
                    chmod($path, 0777);
                }
                $fileinfo = $file;
                $name     = sha1($file->getOriginalName()) . '_' . $data['chunk'] . '.temp';
                $info     = $file->move($path, $path . '/' . $name);

            }

            //如果分片已经上传完，合并分片
            if ($data['chunks'] - 1 == $data['chunk']) {
                //拼接上传目录
                $path = $config['imagePathFormat'];
                $path = str_replace('{y}', date('Y', time()), $path);
                $path = str_replace('{m}', date('m', time()), $path);
                $path = str_replace('{d}', date('d', time()), $path);
                $path = str_replace('{time}', time(), $path);
                $preg = '/{rand:([\d]+)}/';
                preg_match_all($preg, $path, $res);
                if (isset($res[1][0]) && $res[1][0]) {
                    $start = [];
                    $end   = [];
                    for ($i = 0; $i < $res[1][0]; $i++) {
                        $start[] = 1;
                        $end[]   = 9;
                    }
                    $start = (Int) implode('', $start);
                    $end   = (Int) implode('', $end);
                    $path  = str_replace($res[0][0], rand($start, $end), $path);
                }

                //创建目录
                if (!is_readable('.' . $path)) {
                    is_file('.' . $path) or mkdir('.' . $path, 0777, true);
                    chmod('.' . $path, 0777);
                }

                $hashName = str_ireplace("\\", "/", $file->hashName());
                $filename = explode("/", $hashName)[1];
                $savefile = '.' . $path . '/' . $filename;

                //如果文件不存在则创建
                if (!file_exists($savefile)) {
                    fopen($savefile, 'w');
                }

                //开始写入
                $out = @fopen($savefile, "wb");

                $dir = '../runtime' . DIRECTORY_SEPARATOR . $data['md5'];
                $dir = str_ireplace("\\", "/", $dir);
                //增加文件锁
                if (flock($out, LOCK_EX)) {
                    $chunks = $this->getChunks($data['md5']);
                    if (!$chunks) {
                        return ['code' => 0, 'msg' => '上传失败'];
                    }

                    foreach ($chunks as $b) {
                        // 读取文件
                        if (!$in = @fopen($dir . '/' . $b, "rb")) {
                            break;
                        }

                        // 写入文件
                        while ($buff = fread($in, 4096)) {
                            fwrite($out, $buff);
                        }

                        @fclose($in);
                        @unlink($b);
                    }
                    flock($out, LOCK_UN);
                }
                @fclose($out);

                $isWriteData = input('get.isWriteData', 1) == 1 ? true : false;
                if ($isWriteData) {
                    $data = [
                        'name'     => $fileinfo->getOriginalName(),
                        'url'      => $path . '/' . $filename,
                        'type'     => getimagesize('.' . $path . '/' . $filename) ? 0 : 1,
                        'drive_id' => $this->drive_id,
                        'md5'      => $data['md5'],
                    ];
                    $ext         = explode('.', $filename);
                    $data['ext'] = $ext[count($ext) - 1];
                    (new Files())->save($data);
                }
                //删除分片
                $this->removeDirFiles($dir);
                //判断文件是否可执行
                if (is_executable($savefile)) {
                    @unlink($savefile);
                    return ['code' => 0, 'msg' => '文件异常', 'data' => ['status' => 0]];
                }
                $savename[] = $path . '/' . $filename;
                return ['code' => 1, 'msg' => '上传成功', 'data' => ['status' => 1, 'urls' => $savename, 'drive_id' => $this->drive_id]];
            }

            return ['code' => 1, 'msg' => '上传成功', 'data' => ['status' => 0]];
        } catch (\think\Exception $e) {
            return ['code' => 0, 'msg' => $e->getMessage() . $e->getFile() . $e->getLine()];
        }
    }

    /**
     * 文件上传
     * @return json
     */
    public function fileUpload()
    {
        sleep(1);
        // 获取表单上传文件
        $files = request()->file();
        $data  = input('post.');
        if (!isset($data['chunks'])) {
            $data['chunks'] = 1;
        }
        if (!isset($data['chunk'])) {
            $data['chunk'] = 0;
        }
        try {
            if (!$files) {
                return ['code' => 0, 'msg' => '上传文件为空'];
            }
            $config   = Config::config('FileUpload');
            $validate = ['file' => []];
            //拼接图片验证规则
            $validate_rule = array();
            if (isset($config["fileMaxSize"]) && $config["fileMaxSize"]) {
                $validate_rule[] = "fileSize:" . $config["fileMaxSize"];
            }
            if (isset($config["fileAllowFiles"]) && $config["fileAllowFiles"]) {
                $validate_rule[] = "fileExt:" . $config["fileAllowFiles"];
            }

            if ($validate_rule) {
                $validate['file'] = implode("|", $validate_rule);
            }

            try {
                validate($validate)->check($files);
            } catch (\think\exception\ValidateException $e) {
                return ['code' => 0, 'msg' => $e->getError()];
            }

            $fileinfo;
            //上传文件
            foreach ($files as $file) {
                //如果分片还在上传中
                $path = '../runtime' . DIRECTORY_SEPARATOR . $data['md5'];
                $path = str_ireplace("\\", "/", $path);
                //创建目录
                if (!is_readable($path)) {
                    is_file($path) or mkdir($path, 0777, true);
                    chmod($path, 0777);
                }
                $fileinfo = $file;
                $name     = sha1($file->getOriginalName()) . '_' . $data['chunk'] . '.temp';
                $info     = $file->move($path, $path . '/' . $name);

            }

            //如果分片已经上传完，合并分片
            if ($data['chunks'] - 1 == $data['chunk']) {
                //拼接上传目录
                $path = $config['filePathFormat'];
                $path = str_replace('{y}', date('Y', time()), $path);
                $path = str_replace('{m}', date('m', time()), $path);
                $path = str_replace('{d}', date('d', time()), $path);
                $path = str_replace('{time}', time(), $path);
                $preg = '/{rand:([\d]+)}/';
                preg_match_all($preg, $path, $res);
                if (isset($res[1][0]) && $res[1][0]) {
                    $start = [];
                    $end   = [];
                    for ($i = 0; $i < $res[1][0]; $i++) {
                        $start[] = 1;
                        $end[]   = 9;
                    }
                    $start = (Int) implode('', $start);
                    $end   = (Int) implode('', $end);
                    $path  = str_replace($res[0][0], rand($start, $end), $path);
                }

                //创建目录
                if (!is_readable('.' . $path)) {
                    is_file('.' . $path) or mkdir('.' . $path, 0777, true);
                    chmod('.' . $path, 0777);
                }

                $hashName = str_ireplace("\\", "/", $file->hashName());
                $filename = explode("/", $hashName)[1];
                $savefile = '.' . $path . '/' . $filename;

                //如果文件不存在则创建
                if (!file_exists($savefile)) {
                    fopen($savefile, 'w');
                }

                //开始写入
                $out = @fopen($savefile, "wb");

                $dir = '../runtime' . DIRECTORY_SEPARATOR . $data['md5'];
                $dir = str_ireplace("\\", "/", $dir);
                //增加文件锁
                if (flock($out, LOCK_EX)) {
                    $chunks = $this->getChunks($data['md5']);
                    if (!$chunks) {
                        return ['code' => 0, 'msg' => '上传失败'];
                    }

                    foreach ($chunks as $b) {
                        // 读取文件
                        if (!$in = @fopen($dir . '/' . $b, "rb")) {
                            break;
                        }

                        // 写入文件
                        while ($buff = fread($in, 4096)) {
                            fwrite($out, $buff);
                        }

                        @fclose($in);
                        @unlink($b);
                    }
                    flock($out, LOCK_UN);
                }
                @fclose($out);

                $isWriteData = input('get.isWriteData', 1) == 1 ? true : false;
                if ($isWriteData) {
                    $data = [
                        'name'     => $fileinfo->getOriginalName(),
                        'url'      => $path . '/' . $filename,
                        'type'     => getimagesize('.' . $path . '/' . $filename) ? 0 : 1,
                        'drive_id' => $this->drive_id,
                        'md5'      => $data['md5'],
                    ];
                    $ext         = explode('.', $filename);
                    $data['ext'] = $ext[count($ext) - 1];
                    (new Files())->save($data);
                }
                //删除分片
                $this->removeDirFiles($dir);
                //判断文件是否可执行
                if (is_executable($savefile)) {
                    @unlink($savefile);
                    return ['code' => 0, 'msg' => '文件异常', 'data' => ['status' => 0]];
                }
                $savename[] = $path . '/' . $filename;
                return ['code' => 1, 'msg' => '上传成功', 'data' => ['status' => 1, 'urls' => $savename, 'drive_id' => $this->drive_id]];
            }

            return ['code' => 1, 'msg' => '上传成功', 'data' => ['status' => 0]];
        } catch (\think\Exception $e) {
            return ['code' => 0, 'msg' => $e->getMessage() . $e->getFile() . $e->getLine()];
        }
    }

    /**
     * 循环删除目录和文件
     * @param string $dir  文件夹
     * @return bool
     */
    private function removeDirFiles($dir)
    {
        $handle = opendir($dir);
        while ($file = readdir($handle)) {
            //删除所有文件夹
            $type = filetype($dir . "/" . $file);
            if ($file == '.' || $file == "..") {
                continue;
            }

            if ($type == "file") {
                //如果类型为文件，则删除之
                unlink($dir . "/" . $file);
            }
            if ($type == "dir") {
                //如果类型为文件夹，则级联删除
                $this->removeDirFiles($dir . "/" . $file);
            }
        }
        closedir($handle);
        rmdir($dir);
    }

    /**
     * 检测文件md5是否存在，如果存在就是文件已经上传过了
     * @param  string $md5 文件md5
     * @return string 返回已上传的文件数组
     */
    private function checkMd5Exists($md5 = '')
    {
        $find = Files::where(['md5' => $md5])->find();
        if ($find && !file_exists('.' . $find['url'])) {
            $find = null;
            Files::where(['md5' => $md5])->delete();

        }
        return $find;
    }

    /**
     * 获取分片列表
     * @param  string $md5 md5
     * @return [type]         [description]
     */
    public function getChunks($md5)
    {
        $dir = '../runtime' . DIRECTORY_SEPARATOR . $md5;
        $dir = str_ireplace("\\", "/", $dir);

        $chunks = scandir($dir);
        // 除去无用文件
        foreach ($chunks as $key => $block) {
            if ($block == '.' || $block == '..') {
                unset($chunks[$key]);
            }
        }

        //数组按照正常规则排序
        natsort($chunks);
        return $chunks;
    }

    /**
     * 检查分片
     * @param  string $md5 文件md5
     * @return array  分片列表
     */
    public function checkChunks($md5)
    {
        $dir = '../runtime' . DIRECTORY_SEPARATOR . $md5;
        $dir = str_ireplace("\\", "/", $dir);
        //如果目录存在，获取所有分片列表
        if (is_readable($dir)) {
            $chunks = scandir($dir);
            // 除去无用文件
            foreach ($chunks as $key => $block) {
                if ($block == '.' || $block == '..') {
                    unset($chunks[$key]);
                }
            }

            //数组按照正常规则排序
            natsort($chunks);

            $list = [];
            for ($i = 0; $i < count($chunks); $i++) {
                $list[] = (String) $i;
            }
        } else {
            $chunks = [];
            $list   = [];
        }
        $fileinfo = $this->checkMd5Exists($md5);
        return ['code' => 1, 'msg' => '检查成功', 'data' => ['status' => $chunks ? 1 : 0, 'chunks' => $list, 'fileinfo' => $fileinfo]];
    }
}
